package com.hmy.finance.service.statement.impl;


import cn.hutool.core.collection.CollectionUtil;
import com.alibaba.fastjson.JSON;
import com.hmy.finance.api.bo.core.request.enums.FeeTypeEnum;
import com.hmy.finance.common.constants.StatementLossTypeContants;
import com.hmy.finance.common.enums.BusinessBillTypeEnum;
import com.hmy.finance.dal.po.DeviceProfitLossDetailPo;
import com.hmy.finance.dal.po.json.ProfitLossInfoJson;
import com.hmy.finance.service.statement.IReconciliationStatementProviderService;
import com.hmy.finance.service.statement.dto.CreateStatementDeviceDto;
import com.hmy.finance.service.statement.dto.FuturePauseFeeCalculateHandlerDto;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.stream.Collectors;


@Slf4j
@Service
public class IReconciliationStatementProviderServiceImpl implements IReconciliationStatementProviderService {


    /**
     * @param rentPayable          本期租金目前总额
     * @param feeIncurredTime      未来账期最早时间的一天
     * @param currentRentalEndTime 当前账单结束时间（予定时间和退场时间比对取前者）
     * @return 未来账期金额
     */
    @Override
    public BigDecimal futureFeeCalculateHandler(CreateStatementDeviceDto calculateStatementDeviceDto, BigDecimal rentPayable, Integer statementPeriodPauseDays, LocalDateTime feeIncurredTime, LocalDateTime currentRentalEndTime, Map<LocalDate, Double> pauseLossMap) {
        LocalDateTime deviceBeginRentTime = calculateStatementDeviceDto.getDeviceBeginRentTime();
        BigDecimal dailyRentalPrice = calculateStatementDeviceDto.getDailyRentalPrice();
        /**
         * 首个周期月租金额(一个月的价格，不是一天的价格)
         */
        BigDecimal firstCycleMonthlyRentalPrice = calculateStatementDeviceDto.getMonthlyRentalPrice();
        BigDecimal monthlyRentalPrice = calculateStatementDeviceDto.getMonthlyRentalPrice();

        /*1.是否是入场日是否为全租金;*/
        BigDecimal initialDayRentFee = calculateStatementDeviceDto.getInitialDayRentFee();
        if (initialDayRentFee.compareTo(BigDecimal.ZERO) > 0 && initialDayRentFee.compareTo(dailyRentalPrice) < 0) {
            //如果入场首日租金小于日租金，则说明首日入场不是全日计费。需要将首月的费用减去差值作为首月最大值
            BigDecimal lossRent = calculateStatementDeviceDto.getMonthlyRentalPrice().multiply(new BigDecimal(1 - calculateStatementDeviceDto.getInitialDay())).divide(new BigDecimal(30), 2, RoundingMode.HALF_UP);//四舍五入
            firstCycleMonthlyRentalPrice = firstCycleMonthlyRentalPrice.subtract(lossRent);
        }

        /*2.如果是非首次拉取，则需要根已签期账单的金额(设备纬度)是否还是小于月租金(max金额)进行对比，排除掉多个账单拉取中都不满第一周期的情况*/
        if (calculateStatementDeviceDto.getSignedRentFee().compareTo(BigDecimal.ZERO) > 0 && calculateStatementDeviceDto.getSignedRentFee().compareTo(firstCycleMonthlyRentalPrice) < 0) {
            firstCycleMonthlyRentalPrice = firstCycleMonthlyRentalPrice.subtract(calculateStatementDeviceDto.getSignedRentFee());
        }

        BigDecimal plannedRent = rentPayable;
        BigDecimal futurePayable = BigDecimal.ZERO;//未来预计账单
        Integer futureDays = calculateDaysBetween(feeIncurredTime, currentRentalEndTime);

        if (futureDays > 0) {
            //根据latestFeeIncurredTime和currentRentalEndTime返回中间的时间list
            List<LocalDate> futureTimeList = new ArrayList<>();
            LocalDate tempTime = feeIncurredTime.toLocalDate().plusDays(1);
            //设置currentRentalEndTime为23点59分59秒
//            currentRentalEndTime = currentRentalEndTime.withHour(23).withMinute(59).withSecond(59);
            while (!tempTime.isAfter(currentRentalEndTime.toLocalDate())) {
                futureTimeList.add(tempTime);
                tempTime = tempTime.plusDays(1);
            }
            //根据报停账单流水中的报停天数向后推迟日转月时间:
            LocalDate firstCycleFinalDate;
            if (calculateStatementDeviceDto.getInitialDay().equals(new Double(0))) {
                //第一天不算费用。当天不算一天
                firstCycleFinalDate = deviceBeginRentTime.plusDays(30).toLocalDate().plusDays(statementPeriodPauseDays);
            } else {
                //第一天有费用，当天算一天
                firstCycleFinalDate = deviceBeginRentTime.plusDays(30 - 1).toLocalDate().plusDays(statementPeriodPauseDays);
            }

            //对剩余的报停天数进行处理
            FuturePauseFeeCalculateHandlerDto futurePauseFeeCalculateHandlerDto = futurePauseFeeCalculateHandler(futureTimeList, pauseLossMap, firstCycleFinalDate, dailyRentalPrice, firstCycleMonthlyRentalPrice, rentPayable);
            BigDecimal futurePausePayable = futurePauseFeeCalculateHandlerDto.getFuturePausePayable();
            /*对未来的费用添加非全日报停的费用*/
            futurePayable = futurePayable.add(futurePausePayable);
            //那么，第一个周期的上限阈值金额就变成了:月租金金额-首日非全日入场差值-已签署账单金额-第一周期内未来非全日报停费用
            firstCycleMonthlyRentalPrice = firstCycleMonthlyRentalPrice.subtract(futurePauseFeeCalculateHandlerDto.getBeforeFirstCycleDateFuturePausePayable());

            BigDecimal beforeDateFuturePayable = BigDecimal.ZERO;
            BigDecimal afterDateFuturePayable = BigDecimal.ZERO;

            Map<Boolean, List<LocalDate>> map = futureTimeList.stream().collect(Collectors.partitioningBy(dateTime -> !dateTime.isAfter(firstCycleFinalDate)));
            for (Map.Entry<Boolean, List<LocalDate>> entry : map.entrySet()) {
                Boolean isBefore = entry.getKey();
                if (isBefore) {
                    /*step2.1如果小于1，未来账单跨周期情况下:说明账单在第一个月，但是会跨到第N个月，则判断累加的租金是否大于月租金，如果大于月租金，则对未来账单进行补。判断日转月*/
                    List<LocalDate> firstPeriod = entry.getValue();//第一个账单周期
                    firstPeriod.sort(LocalDate::compareTo);
                    for (LocalDate dateTime : firstPeriod) {
                        log.info("dateTime:{}", JSON.toJSON(dateTime));
                        if (plannedRent.add(dailyRentalPrice).compareTo(firstCycleMonthlyRentalPrice) < 0) {
                            plannedRent = plannedRent.add(dailyRentalPrice);
                            beforeDateFuturePayable = beforeDateFuturePayable.add(dailyRentalPrice);
                        } else {
                            plannedRent = firstCycleMonthlyRentalPrice;
                            beforeDateFuturePayable = firstCycleMonthlyRentalPrice.subtract(rentPayable);//减去已经计算的租金
                            break;
                        }
                    }
                } else {
                    /*step2.2如果小于1，未来账单不跨周期情况下:说明账单还是在第一个月，则判断累加的租金是否大于月租金，如果大于月租金，则对未来账单进行补。判断日转月*/
                    afterDateFuturePayable = afterDateFuturePayable.add(monthlyRentalPrice.divide(new BigDecimal(30), 3, BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(entry.getValue().size())));
                }
            }
            futurePayable = futurePayable.add(beforeDateFuturePayable).add(afterDateFuturePayable);
        }
        //未来费用=未来租金预计(天数=未来天数-报停天数)+未来非全日报停费用。其中报停天数不用管全日还是非全日，先减去只要有报停的天数,再加上非全日报停的费用即可
        return futurePayable;
    }

    /**
     * @param futureTimeList               未来账期时间
     * @param pauseLossMap                 报停map：key:报停日期;value:报停系数（1为全日报停）
     * @param firstCycleFinalDate          日转月日期
     * @param dailyRentalPrice             日租金
     * @param firstCycleMonthlyRentalPrice 首轮周期月租金（月租金-首日入场差额-已签署账单金额）
     * @return 未来报停费用
     */
    @Override
    public FuturePauseFeeCalculateHandlerDto futurePauseFeeCalculateHandler(List<LocalDate> futureTimeList, Map<LocalDate, Double> pauseLossMap, LocalDate firstCycleFinalDate, BigDecimal dailyRentalPrice, BigDecimal firstCycleMonthlyRentalPrice, BigDecimal rentPayable) {
        FuturePauseFeeCalculateHandlerDto futurePauseFeeCalculateHandlerDto = new FuturePauseFeeCalculateHandlerDto();

        BigDecimal futurePausePayable = BigDecimal.ZERO;
        BigDecimal plannedRent = BigDecimal.ZERO;//rentPayable

        BigDecimal beforeFirstCycleDateFuturePausePayable = BigDecimal.ZERO;
        BigDecimal beforeFirstCycleDateFuturePauseDays = BigDecimal.ZERO;
        BigDecimal afterFirstCycleDateFuturePausePayable = BigDecimal.ZERO;
        BigDecimal afterFirstCycleDateFuturePauseDays = BigDecimal.ZERO;
        //对futureTimeList进行排序
        futureTimeList.stream().sorted(Comparator.naturalOrder()).collect(Collectors.toList());

        List<LocalDate> datesToRemove = new ArrayList<>();
        for (LocalDate futureDate : futureTimeList) {
            //pauseActualFeeRatio 实际费用比例：1为不报停，0为全日报停
            BigDecimal pauseActualFeeRatio = BigDecimal.ONE.subtract(new BigDecimal(pauseLossMap.getOrDefault(futureDate, new Double(0.0))));//实际费用计算比例(1-报停比例)
            BigDecimal pauseActualFee = BigDecimal.ZERO;//报停后产生的实际费用
            if (!futureDate.isAfter(firstCycleFinalDate)) {
                //早于或等于日转月时间
                //pauseActualFeeRatio在大于等于0小于1:(0<pauseActualFeeRatio<1)对应部分报停&(pauseActualFeeRatio=0)对应全日报停
                if (pauseActualFeeRatio.compareTo(new BigDecimal(1)) < 0) {
                    pauseActualFee = dailyRentalPrice.multiply(pauseActualFeeRatio);
                    if (pauseActualFeeRatio.compareTo(new BigDecimal(0)) == 0) {
                        //全日报停情况下，需要将日转月时间往后延一天
                        firstCycleFinalDate = firstCycleFinalDate.plusDays(1);//day2MonthDate日转月时间往后延一天
                    }
                    beforeFirstCycleDateFuturePauseDays = beforeFirstCycleDateFuturePauseDays.add(BigDecimal.ONE.subtract(pauseActualFeeRatio));
                    datesToRemove.add(futureDate);//未来周期里这天已经被部分报停，所以不用再计算未来账单
                    log.info("日期:{},报停后计算租金为:{}", futureDate, pauseActualFee);
                }
                if (plannedRent.add(pauseActualFee).compareTo(firstCycleMonthlyRentalPrice) < 0) {
                    plannedRent = plannedRent.add(pauseActualFee);
                    beforeFirstCycleDateFuturePausePayable = beforeFirstCycleDateFuturePausePayable.add(pauseActualFee);
                } else {
                    plannedRent = firstCycleMonthlyRentalPrice;
                    beforeFirstCycleDateFuturePausePayable = firstCycleMonthlyRentalPrice.subtract(rentPayable);//减去已经计算的租金
                }
            } else {
                //晚于日转月时间
                //pauseActualFeeRatio在大于等于0小于1:(0<pauseActualFeeRatio<1)对应部分报停&(pauseActualFeeRatio=0)对应全日报停
                if (pauseActualFeeRatio.compareTo(new BigDecimal(1)) < 0) {
                    //晚于日转月时间
                    pauseActualFee = firstCycleMonthlyRentalPrice.multiply(pauseActualFeeRatio.setScale(1, BigDecimal.ROUND_HALF_UP)).divide(new BigDecimal(30), 3, BigDecimal.ROUND_HALF_UP);
                    afterFirstCycleDateFuturePauseDays = afterFirstCycleDateFuturePauseDays.add(BigDecimal.ONE.subtract(pauseActualFeeRatio));
                    datesToRemove.add(futureDate); //未来周期里这天已经被报停，所以不用再计算未来账单
                    afterFirstCycleDateFuturePausePayable = afterFirstCycleDateFuturePausePayable.add(pauseActualFee);
                    log.info("日期:{},报停后计算租金为:{}", futureDate, pauseActualFee);
                }
            }
        }
        futureTimeList.removeAll(datesToRemove);
        futurePausePayable = futurePausePayable.add(beforeFirstCycleDateFuturePausePayable).add(afterFirstCycleDateFuturePausePayable);
        log.info("futurePauseFeeCalculateHandler 计算未来报停总费用为:{}", futurePausePayable);

        futurePauseFeeCalculateHandlerDto.setBeforeFirstCycleDateFuturePausePayable(beforeFirstCycleDateFuturePausePayable);
        futurePauseFeeCalculateHandlerDto.setBeforeFirstCycleDateFuturePauseDays(beforeFirstCycleDateFuturePauseDays);
        futurePauseFeeCalculateHandlerDto.setAfterFirstCycleDateFuturePausePayable(afterFirstCycleDateFuturePausePayable);
        futurePauseFeeCalculateHandlerDto.setAfterFirstCycleDateFuturePauseDays(afterFirstCycleDateFuturePauseDays);
        futurePauseFeeCalculateHandlerDto.setFuturePausePayable(futurePausePayable);
        return futurePauseFeeCalculateHandlerDto;
    }

    @Override
    public BigDecimal getProfitLossSubtotalWithDeviceLoss(List<DeviceProfitLossDetailPo> profitLossInfos, String billType) {
        BigDecimal profitLossSubtotal = new BigDecimal(0);
        if (CollectionUtil.isEmpty(profitLossInfos)) {
            return profitLossSubtotal;//如果损益单list为空，则直接返回0
        }

        for (DeviceProfitLossDetailPo deviceProfitLossDetail : profitLossInfos) {
            /*进场运费损益*/
            if (billType.equals(StatementLossTypeContants.ENTRY_SHIPPING_MAPPING)) {
                if (deviceProfitLossDetail.getBusinessBillType().equals(BusinessBillTypeEnum.ENTRY) && deviceProfitLossDetail.getFeeType().equals(FeeTypeEnum.SHIPPING_FEE)) {
                    profitLossSubtotal = profitLossSubtotal.add(deviceProfitLossDetail.getFee());//如果是运费，则加上运费
                }
                if (deviceProfitLossDetail.getBusinessBillType().equals(BusinessBillTypeEnum.ENTRY) && deviceProfitLossDetail.getFeeType().equals(FeeTypeEnum.SHIPPING_FEE_WAIVER)) {
                    profitLossSubtotal = profitLossSubtotal.subtract(deviceProfitLossDetail.getFee());//如果是运费减免，则减去运费
                }
            }
            /*退场运费损益*/
            if (billType.equals(StatementLossTypeContants.EXIT_SHIPPING_MAPPING)) {
                if (deviceProfitLossDetail.getBusinessBillType().equals(BusinessBillTypeEnum.EXIT) && deviceProfitLossDetail.getFeeType().equals(FeeTypeEnum.SHIPPING_FEE)) {
                    profitLossSubtotal = profitLossSubtotal.add(deviceProfitLossDetail.getFee());//如果是运费，则加上运费
                }
                if (deviceProfitLossDetail.getBusinessBillType().equals(BusinessBillTypeEnum.EXIT) && deviceProfitLossDetail.getFeeType().equals(FeeTypeEnum.SHIPPING_FEE_WAIVER)) {
                    profitLossSubtotal = profitLossSubtotal.subtract(deviceProfitLossDetail.getFee());//如果是运费减免，则减去运费
                }
            }
            /*进场改装费损益(不管进退场)*/
            if (billType.equals(StatementLossTypeContants.ENTRY_MODIFICATION_MAPPING)) {
                if (deviceProfitLossDetail.getFeeType().equals(FeeTypeEnum.MODIFICATION_FEE)) {
                    profitLossSubtotal = profitLossSubtotal.add(deviceProfitLossDetail.getFee());//如果是改装费，则加上改装费
                }
                if (deviceProfitLossDetail.getFeeType().equals(FeeTypeEnum.MODIFICATION_FEE_WAIVER)) {
                    profitLossSubtotal = profitLossSubtotal.subtract(deviceProfitLossDetail.getFee());//如果是改装费减免，则减去改装费
                }
            }
        }
        return profitLossSubtotal;
    }

    /**
     * 计算各类损益小计，这个函数写的真鸡儿难看，但是没啥好办法
     *
     * @param profitLossInfos 损益信息
     * @param billType        损益类型
     * @return profitLossSubtotal
     */

    @Override
    public BigDecimal getProfitLossSubtotal(List<ProfitLossInfoJson> profitLossInfos, String billType) {
        BigDecimal profitLossSubtotal = new BigDecimal(0);
        if (CollectionUtil.isEmpty(profitLossInfos)) {
            return profitLossSubtotal;//如果损益单list为空，则直接返回0
        }
        for (ProfitLossInfoJson profitLossInfoJson : profitLossInfos) {
//            /*进场运费损益*/
//            if (billType.equals(ENTRY_SHIPPING_MAPPING)) {
//                if (profitLossInfoJson.getBusinessBillType().equals(BusinessBillTypeEnum.ENTRY.getValue()) && profitLossInfoJson.getType().equals(FeeTypeEnum.SHIPPING_FEE.getValue())) {
//                    profitLossSubtotal = profitLossSubtotal.add(profitLossInfoJson.getFee());//如果是运费，则加上运费
//                }
//                if (profitLossInfoJson.getBusinessBillType().equals(BusinessBillTypeEnum.ENTRY.getValue()) && profitLossInfoJson.getType().equals(FeeTypeEnum.SHIPPING_FEE_WAIVER.getValue())) {
//                    profitLossSubtotal = profitLossSubtotal.subtract(profitLossInfoJson.getFee());//如果是运费减免，则减去运费
//                }
//            }
//            /*退场运费损益*/
//            if (billType.equals(EXIT_SHIPPING_MAPPING)) {
//                if (profitLossInfoJson.getBusinessBillType().equals(BusinessBillTypeEnum.EXIT.getValue()) && profitLossInfoJson.getType().equals(FeeTypeEnum.SHIPPING_FEE.getValue())) {
//                    profitLossSubtotal = profitLossSubtotal.add(profitLossInfoJson.getFee());//如果是运费，则加上运费
//                }
//                if (profitLossInfoJson.getBusinessBillType().equals(BusinessBillTypeEnum.EXIT.getValue()) && profitLossInfoJson.getType().equals(FeeTypeEnum.SHIPPING_FEE_WAIVER.getValue())) {
//                    profitLossSubtotal = profitLossSubtotal.subtract(profitLossInfoJson.getFee());//如果是运费减免，则减去运费
//                }
//            }
//            /*进场改装费损益(不管进退场)*/
//            if (billType.equals(ENTRY_MODIFICATION_MAPPING)) {
//                if (profitLossInfoJson.getType().equals(FeeTypeEnum.MODIFICATION_FEE.getValue())) {
//                    profitLossSubtotal = profitLossSubtotal.add(profitLossInfoJson.getFee());//如果是改装费，则加上改装费
//                }
//                if (profitLossInfoJson.getBusinessBillType().equals(BusinessBillTypeEnum.ENTRY.getValue()) && profitLossInfoJson.getType().equals(FeeTypeEnum.MODIFICATION_FEE_WAIVER.getValue())) {
//                    profitLossSubtotal = profitLossSubtotal.subtract(profitLossInfoJson.getFee());//如果是改装费减免，则减去改装费
//                }
//            }
            /*索赔单损益(不管进退场)*/
            if (billType.equals(StatementLossTypeContants.CLAIMS_MAPPING)) {
                if (profitLossInfoJson.getType().equals(FeeTypeEnum.CLAIMS_FEE.getValue())) {
                    profitLossSubtotal = profitLossSubtotal.add(profitLossInfoJson.getFee());//如果是索赔费用，则加上索赔费用
                }
                if (profitLossInfoJson.getType().equals(FeeTypeEnum.CLAIMS_FEE_WAIVER.getValue())) {
                    profitLossSubtotal = profitLossSubtotal.subtract(profitLossInfoJson.getFee());//如果是索赔费用减免，则减去索赔费用
                }
            }
            /*报停单损益(不管进退场)*/
            if (billType.equals(StatementLossTypeContants.PAUSE_MAPPING)) {
                if (profitLossInfoJson.getType().equals(FeeTypeEnum.SUPPLEMENTARY_PAUSE_FEE.getValue())) {
                    profitLossSubtotal = profitLossSubtotal.subtract(profitLossInfoJson.getFee());//补录报停，加上报停费用
                }
            }

            /*入场日非全日费用(比如入场那天只算半天费用)*/
            if (billType.equals(StatementLossTypeContants.INITIAL_DAY_FEE)) {
                if (profitLossInfoJson.getType().equals(FeeTypeEnum.INITIAL_DAY_FEE.getValue())) {
                    profitLossSubtotal = profitLossSubtotal.add(profitLossInfoJson.getFee());
                }
                if (profitLossInfoJson.getType().equals(FeeTypeEnum.INITIAL_DAY_FEE_WAIVER.getValue())) {
                    profitLossSubtotal = profitLossSubtotal.subtract(profitLossInfoJson.getFee());
                }
            }
        }
        return profitLossSubtotal;
    }

    /**
     * 计算两个时间之间的天数
     */
    @Override
    public Integer calculateDaysBetween(LocalDateTime startTime, LocalDateTime endTime) {
        LocalDate startDate = startTime.toLocalDate();
        LocalDate endDate = endTime.toLocalDate();
        return Integer.parseInt(String.valueOf(ChronoUnit.DAYS.between(startDate, endDate)));
    }

}
